#define MXMODULE_REGION

#include "detk/drs.h"
#include "detk/region.h"
#include <assert.h>

#define MX__DRS_CHUNK 64

static MX_RECT mx__drs_area;
static unsigned mx__drs_started = 0;
static unsigned mx__drs_dirtychanged = 0;
static MX_REGION mx__drs_dirtyinverse = { {0, 0, 0} };
static MX_REGION mx__drs_dirty = { {0, 0, 0} };

static void mx__drs_stop(void)
{
	if (mx__drs_dirtyinverse.data)
		mx_vector_free(&mx__drs_dirtyinverse);

	if (mx__drs_dirty.data)
		mx_vector_free(&mx__drs_dirty);

	mx__drs_started = 0;
}

static void mx__drs_start(void)
{
	assert(!mx__drs_started);

	mx_vector(&mx__drs_dirtyinverse);
	mx_vector_reserve(&mx__drs_dirtyinverse, MX__DRS_CHUNK);

	mx_vector(&mx__drs_dirty);
	mx_vector_reserve(&mx__drs_dirty, MX__DRS_CHUNK);

	atexit(mx__drs_stop);
	mx__drs_started = 1;
}

/** !Initialize dirty-rectangle system
This function initializes the dirty-rectangle updating system for a
screen of the given dimensions. */
void mx_drs_area(int w, int h)
{
	if (!mx__drs_started)
		mx__drs_start();

	mx__drs_area.x1 = 0;
	mx__drs_area.y1 = 0;
	mx__drs_area.x2 = w;
	mx__drs_area.y2 = h;

	mx_vector_resize(&mx__drs_dirtyinverse, 0);
	mx__drs_dirtychanged = 1;
}

/** !Return if parts of the screen need updating
This function returns zero if there are no dirty areas of the screen.
If the function return false then a following call to mx_drs_update() will
not call the flush callback.  This is useful to determine if a call to
mx_drs_update() could (potentially) update the screen.  It is not guaranteed
that if this function returns true that mx_drs_update() will in fact call
the fluch callback function. */
unsigned mx_drs_dirtychanged(void)
{
	return mx__drs_dirtychanged;
}

/** !Call the flush function with the parts of the screen needing updating
This function calls a user defined callback function with the areas
of the screen marked as dirty by the mx_drs_dirty() function. */
unsigned mx_drs_update(MX_FLUSH_FUNC dflush)
{
	unsigned int i;
	unsigned drawn = 0;

	if (!mx__drs_started)
		mx__drs_start();

	/** This functions return zero if no portion of the screen requires
        updating. */
	if (!mx__drs_dirtychanged)
		return 0;

	/** The updated areas (after possible modification in the callback) are
    guaranteed to bo non-overlapping. */
	mx_vector_resize(&mx__drs_dirty, 0);
	mx_vector_reserve(&mx__drs_dirty, MX__DRS_CHUNK);
	mx_vector_append(&mx__drs_dirty, &mx__drs_area, 1);

	for (i = 0; i < mx_vector_size(&mx__drs_dirtyinverse); i++)
		mx_region_clip(&mx__drs_dirty, &mx__drs_dirtyinverse.data[i]);

	/* Keep at least some places allocated so updating goes fast. If this is
	   done before the actual redraw/flush call then we can call mx_drs_dirty
	   inside an expose event without causing the update to get lost or create
	   an infinite loop. */
	mx_vector_resize(&mx__drs_dirtyinverse, MX__DRS_CHUNK);
	mx_vector_contract(&mx__drs_dirtyinverse);
	mx_vector_resize(&mx__drs_dirtyinverse, 0);
	mx_vector_append(&mx__drs_dirtyinverse, &mx__drs_area, 1);
	mx__drs_dirtychanged = 0;

	/* Update the areas one by one */
	while (mx_vector_size(&mx__drs_dirty)) {
		MX_RECT rrect = mx__drs_dirty.data[0];

		if (dflush)
			dflush(&rrect);

		/** The callback function may modify the area to indicate the area
            actually updated. */
		mx_region_clip(&mx__drs_dirty, &rrect);
		drawn = 1;
	}

	/** After this function returns the entire dirty area of the screen will have
    been updated though the callback and the entire screen will be marked as
    clean. */

	return drawn;
}

/** !Mark a portion of the screen as needing updating
This function marks a portion of the screen as dirty or clean. */
void mx_drs_dirty(const MX_RECT * rrect, unsigned mark)
{
	if (!mx__drs_started)
		mx__drs_start();

	assert(rrect);

	/* Clip from the inverse means to mark as dirty */
	mx_region_clip(&mx__drs_dirtyinverse, rrect);

	/* Add to inverse means mark as clean */
	if (!mark)
		mx_vector_append(&mx__drs_dirtyinverse, rrect, 1);

	mx__drs_dirtychanged = 1;
}
